#include <os/list.h>
#include <os/fs.h>
#include <sbi.h>
#include <os/mm.h>
#include <os/time.h>
#include <screen.h>
#include <pgtable.h>
#include <os/stdio.h>
#include <os/string.h>

inode_t *current_inode;
int state = 0;

void clears(uintptr_t mem_addr){
    int i = 0;
    for(i = 0; i < 512; i+=8){
        *((u64*)(mem_addr+i)) = 0;
    }
}

/*Serch bmap and get free data block*/
u32 alloc_data_block(){
    sbi_sd_read(BMAP_MEM_ADDR, 32, FS_START + BMAP_SD_OFFST);
    u8 *bm = (u8*)pa2kva(BMAP_MEM_ADDR);
    u32 fb = 0;
    for(int i = 0; i < 32*512; i++){
        for(int j = 0; j < 8; j++){
            if(!(bm[i] & (1 << j))){
                fb = 8*i + j;
                //Set bit 1
                bm[i] |= (1 << j);
                sbi_sd_write(BMAP_MEM_ADDR, 32, FS_START + BMAP_SD_OFFST);
                return fb;
            }
        }
    }
    return 0;
}

void clear_data_block(u32 block_id){
    sbi_sd_read(BMAP_MEM_ADDR, 32, FS_START + BMAP_SD_OFFST);
    u8 *bm = (u8*)pa2kva(BMAP_MEM_ADDR);
    bm[block_id/8] &= ~(1 << (block_id % 8));
    sbi_sd_write(BMAP_MEM_ADDR, 32, FS_START + BMAP_SD_OFFST);
}

u32 alloc_inode(){
    sbi_sd_read(IMAP_MEM_ADDR, 1, FS_START + IMAP_SD_OFFSET);
    u8 *im = (u8*)(pa2kva(IMAP_MEM_ADDR));
    u32 ret_inode = 0;
    for(int i = 0; i < 512; i++){
        for(int j = 0; j < 8; j++){
            if(!(im[i] & (1 << j))){
                ret_inode = 8*i + j;
                //set bit 1
                im[i] |= (1 << j);
                sbi_sd_write(IMAP_MEM_ADDR, 1, FS_START + IMAP_SD_OFFSET);
                return ret_inode;
            }
        }
    }
}

void clear_inode_map(u32 ino){
    sbi_sd_read(IMAP_MEM_ADDR, 1, FS_START + IMAP_SD_OFFSET);
    u8 *im = (u8*)(pa2kva(IMAP_MEM_ADDR));
    im[ino/8] &= ~(1 << (ino % 8));
    sbi_sd_write(IMAP_MEM_ADDR, 1, FS_START + IMAP_SD_OFFSET); 
}

/*Write a inode into our disk*/
void write_inode(u32 ino){
    sbi_sd_write(INODE_MEM_ADDR + ino*512, 1, FS_START + INODE_SD_OFFSET + ino);
}

/*Init . and .. for dir, 2*32 = 64B*/
void init_dentry(u32 block_num, u32 ino, u32 pino){
    dentry_t *d = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    clears(d);
    d[0].mode = DIR;
    d[0].ino  = ino;
    kstrcpy(d[0].name, ".");

    d[1].mode = DIR;
    d[1].ino  = pino;
    kstrcpy(d[1].name, "..");

    sbi_sd_write(DATA_MEM_ADDR, 8, FS_START + block_num*8);
}

inode_t *get_inode_from_ino(u32 ino){
    sbi_sd_read(INODE_MEM_ADDR + ino*512, 1, FS_START + INODE_SD_OFFSET + ino);
    inode_t *find_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR + ino*512));
    return find_inode;
}

inode_t *lookup(inode_t *parent_dp, char *name, int mode){

    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + (parent_dp -> direct_ptr[0])*8);
    dentry_t *d = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    //We can only create a dir in a dir
    if(d -> mode != DIR){
        printk(">Error: You can only create a file or dir in a dir");
        return;
    }
    for(int i = 0; i < parent_dp -> usize; i += 32){
        if((d[i/32].mode == mode) && (!strcmp(d[i/32].name, name))){
            inode_t *find_inode = get_inode_from_ino(d[i/32].ino);
            return find_inode;
        }
    }
    return NULL;
}

//to find path, we use recurssion
int find  = 0;
int depth = 0;
int find_dir(char *path, int mode){
    depth = 0;
    int i = 0;
    int j = 0;
    if(depth == 0){
        find = 0;
    }
    /*If absolute path*/
    if(path[0] == '/'){
        sbi_sd_read(INODE_MEM_ADDR, 1, FS_START + INODE_SD_OFFSET);
        current_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
        if(path[1] == '\0'){
            return 1;
        }
        for(i = 0; i < kstrlen(path); i++){
            path[i] = path[i+1];
        }
        path[i] = '\0';
    }
    char head[20];
    char tail[100];
    for(i = 0; i < kstrlen(path) && path[i] != '/'; i++){
        head[i] = path[i];
    }
    head[i++] = '\0';
    for(j = 0; i < kstrlen(path); i++, j++){
        tail[j] = path[i]; 
    }
    tail[j] = '\0';
    
    inode_t *find_inode;
    if((find_inode = lookup(current_inode, head, DIR)) != 0){
        depth ++;
        current_inode = find_inode;
        if(tail[0] == '\0'){
            find = 1;
            return 1;
        }
        find_dir(tail, DIR);
    }
    if(find  == 1){
        depth --;
        return 1;
    }else{
        depth = 0;
        return 0;
    }
}

//syscall
void do_mkfs(){
    int i;
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    screen_reflush();
    printk("[FS] Start initializing filesystem!\n");

    //Superblock init!
    printk("[FS] Setting superblock...\n");
    clears(sp);
    //fill SP
    sp -> magic    = MAGIC;
    sp -> fs_size  = FS_SIZE;
    sp -> fs_start = FS_START;
    sp -> block_map_num = 32;
    sp -> block_map_start = BMAP_SD_OFFST;
    sp -> inode_map_num = 1;
    sp -> inode_map_start = IMAP_SD_OFFSET;
    sp -> inode_num = 512;
    sp -> inode_start = INODE_SD_OFFSET;
    sp -> data_block_num = 1048576 - 545;
    sp -> data_block_start = DATA_SD_OFFSET;
    sp -> isize = ISZIE;
    sp -> dsize = DSIZE;
    printk("[FS] magic: 0x%x\n", sp -> magic);
    printk("[FS] num sector: %d, start sector: %d\n",sp -> fs_size, sp -> fs_start);
    printk("[FS] block map offset: %d(%d)\n", sp -> block_map_start, sp -> block_map_num);
    printk("[FS] inode map offset: %d(%d)\n", sp -> inode_map_start, sp -> inode_map_num);
    printk("[FS] inode offset: %d(%d)\n", sp -> inode_start, sp -> inode_num);
    printk("[FS] data offset: %d(%d)\n",sp -> data_block_start, sp -> data_block_num);
    printk("[FS] inode entry size: %dB, dir entry size: %dB\n", sp -> isize, sp -> dsize);
    printk("IENTRY SIZE:%d\n");

    //set block map
    printk("[FS] Setting block map...\n");
    u8 *bm = (u8*)(pa2kva(BMAP_MEM_ADDR));
    for(i = 0; i < sp -> block_map_num; i++){
        clears(bm + SS*i);
    }
    //Note: (512+2+32)/8 + 1 = 69
    for(i = 0; i < 69; i++){
        bm[i/8] = bm[i/8] | (1 << (i % 8));
    }
    //set inode map
    printk("[FS] Setting inode map...\n");
    u8 *im = (u8*)(pa2kva(IMAP_MEM_ADDR));
    clears(im);
    im[0] |= 1; /*dir*/
    sbi_sd_write(SB_MEM_ADDR, 34/*sp+im+bm*/, FS_START);

    //Setting inode
    printk("[FS] Setting inode...\n");
    inode_t *inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
    inode[0].ino    = 0;
    inode[0].mode   = DIR;
    inode[0].access = RW_A;
    inode[0].nlinks = 0;
    inode[0].size   = 4096;
    inode[0].usize  = 64;       //Two dentry
    inode[0].stime  = get_timer();
    inode[0].mtime  = get_timer();
    inode[0].lev1_ptr = 0;
    inode[0].lev2_ptr = 0;
    inode[0].direct_ptr[0] = alloc_data_block();
    for(i = 1; i < DPTR; i++){
        inode[0].direct_ptr[i] = 0;
    }
    write_inode(inode[0].ino);
    //Init dentry
    init_dentry(inode[0].direct_ptr[0], inode[0].ino, 0);

    printk("Initializing filesystem finished!\n");
    screen_reflush();
    current_inode = inode;
}

void do_statfs(){
    sbi_sd_read(SB_MEM_ADDR, 1, FS_START);
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    printk("\n");
    //check sp
    if(sp -> magic != MAGIC){
        printk(">ERROR: No File System!\n");
        return;
    }
    
    
    int i,j;
    int used_block = 0;
    int used_inode = 0;
    //used block
    sbi_sd_read(BMAP_MEM_ADDR, 32, FS_START + BMAP_SD_OFFST);
    u8 *bm = (u8*)(pa2kva(BMAP_MEM_ADDR));
    for(i = 0; i < 32*512; i++){
        for(j = 0; j < 8; j++){
            used_block += (bm[i] >> j) & 0x1;
        }
    }
    //used inode
    sbi_sd_read(IMAP_MEM_ADDR, 1, FS_START + IMAP_SD_OFFSET);
    u8 *im = (u8*)(pa2kva(IMAP_MEM_ADDR));
    for(i = 0; i < 512; i++){
        for(j = 0; j < 8; j++){
            used_inode += (im[i] >> j) & 0x1;
        }
    }
    //screen_move_cursor(1,1);
    printk("magic: 0x%x\n",sp -> magic);
    printk("used block: %d/%d, start sector: %d(0x%x)\n",used_block, FS_SIZE, FS_START, FS_START);
    printk("inode map offset: %d, occupied sector: %d, used: %d/512\n",sp -> inode_map_start, sp -> inode_map_num, used_inode);
    printk("block map offset: %d, occupied block: %d\n", sp -> block_map_start, sp -> block_map_num);
    printk("inode offset: %d, occupied sector: %d\n", sp -> inode_start, sp -> inode_num);
    printk("data offset: %d, occupied sector: %d\n", sp -> data_block_start, sp -> data_block_num);
    printk("inode entry size: %dB, dir entry size: %dB\n", sp -> isize, sp -> dsize);
}

//mkdir
void do_mkdir(char *name){
    //check if there is fs
    sbi_sd_read(SB_MEM_ADDR, 1, FS_START);
    printk("\n");
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    if(sp -> magic != MAGIC){
        printk("> Error: Fs does not exit!\n");
        return;
    }
    //Step1: check if this dir exit
    sbi_sd_read(INODE_MEM_ADDR, 1, FS_START + INODE_SD_OFFSET);
    if(state == 0){
        current_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
        state = 1;
    }
    inode_t *father_dir = current_inode;      //Father
    inode_t *find_dir   = lookup(father_dir, name, DIR);
    if(find_dir != NULL){
        printk("This DIR has already exited\n");
        return;
    }
    //Step 2: alloc an inode and write inode map bit 1
    u32 inode_snum = alloc_inode();
    if(inode_snum == 0){
        printk(">ERROR: No Available Inode!\n");
        return;
    }
    inode_t *new_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR) + inode_snum*512);
    new_inode -> ino    = inode_snum;
    new_inode -> mode   = DIR;
    new_inode -> access = RW_A;
    new_inode -> nlinks = 0;
    new_inode -> size   = 4096;
    new_inode -> usize  = 64; //. & .. dir
    new_inode -> stime = new_inode -> mtime = get_timer();
    new_inode -> direct_ptr[0] = alloc_data_block();  
    for(int i = 1; i < DPTR; i++){
        new_inode -> direct_ptr[i] = 0;
    }
    new_inode -> lev1_ptr = 0;
    new_inode -> lev2_ptr = 0;
    write_inode(new_inode -> ino);
    //Create dentry . and ..
    init_dentry(new_inode -> direct_ptr[0], new_inode -> ino, father_dir -> ino);
    //add dentry in its parent dir
    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + (father_dir -> direct_ptr[0])*8);
    dentry_t *dentry = (dentry_t*)(pa2kva(DATA_MEM_ADDR) + father_dir -> usize); //find unused addr
    dentry -> mode = DIR;
    dentry -> ino  = new_inode -> ino;
    kstrcpy(dentry -> name, name);
    sbi_sd_write(DATA_MEM_ADDR, 8, FS_START + (father_dir -> direct_ptr[0])*8);
    //update father inode
    father_dir -> mtime = get_timer();
    father_dir -> nlinks ++;
    father_dir -> usize += 32;
    write_inode(father_dir -> ino);
    //debugging
    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + 69*8);
    dentry = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    //printk("Dentry[2]:%s",dentry[2].name);
    //printk("Dentry[3]:%s",dentry[3].name);
    //debugging end
    printk("Successfully make a directory!\n");
}



//change directory
void do_cd(char *path){
    //check if there is fs
    sbi_sd_read(SB_MEM_ADDR, 1, FS_START);
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    if(sp -> magic != MAGIC){
        printk("File System does not exit!\n");
        return;
    }

    sbi_sd_read(INODE_MEM_ADDR, 1, FS_START + INODE_SD_OFFSET);
    if(state == 0){
        current_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
        state = 1;
    }

    inode_t *temp = current_inode;
    if(path[0] != '\0'){
        if(find_dir(path, DIR) == 0 || current_inode -> mode != DIR){
            current_inode = temp;
            printk(">ERROR: Cannot find this directory");
            return;
        }
    }
}

void do_ls(){
    sbi_sd_read(SB_MEM_ADDR, 1, FS_START);
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    if(sp -> magic != MAGIC){
        printk("fs does not exit!\n");
        return;
    }
    sbi_sd_read(INODE_MEM_ADDR, 1, FS_START + INODE_SD_OFFSET);
    if(state == 0){
        current_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
        state = 1;
    }
    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + (current_inode -> direct_ptr[0])*8);
    dentry_t *d = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    printk("\n");
    for(int i = 0; i < current_inode -> usize; i+=32){
        printk("%s ", d[i/32].name);
    }
    printk("\n");
}

void do_rmdir(char *name){
    //check fs
    int i;
    sbi_sd_read(SB_MEM_ADDR, 1, FS_START);
    superblock_t *sp = (superblock_t*)(pa2kva(SB_MEM_ADDR));
    if(sp -> magic != MAGIC){
        printk("File system does not exit!\n");
        return;
    }
    //check current_node, if not ---> root dir
    sbi_sd_read(INODE_MEM_ADDR, 1, FS_START + INODE_SD_OFFSET);
    if(state == 0){
        current_inode = (inode_t*)(pa2kva(INODE_MEM_ADDR));
        state = 1;
    }

    inode_t *father_inode = current_inode;
    inode_t *find_dir = lookup(father_inode, name, DIR);
    if(find_dir == NULL){
        printk(">ERROR: Cannot find this dir!\n");
        return;
    }
    //clear block map
    clear_data_block(find_dir -> direct_ptr[0]);
    //clear inode map
    clear_inode_map(find_dir -> ino);
    //delete file
    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + (father_inode -> direct_ptr[0])*8);
    dentry_t *d = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    int found = 0;
    for(i = 0; i < father_inode -> usize; i+= 32){
        if(found){
            memcpy((u8*)(d - 32), (u8*)d, 32);
        }
        if(find_dir -> ino == d -> ino){
            found = 1;
        }
        d++;
    }
    sbi_sd_write(DATA_MEM_ADDR, 8, FS_START + (father_inode -> direct_ptr[0])*8);
    //delete all files
    sbi_sd_read(DATA_MEM_ADDR, 8, FS_START + (find_dir -> direct_ptr[0])*8);
    dentry_t *rm_d = (dentry_t*)(pa2kva(DATA_MEM_ADDR));
    for(i = 0; i < find_dir -> usize; i+=32){
        if(rm_d[i/32].mode == FILE){
            inode_t *find_file = lookup(father_inode, rm_d[i/32].name, FILE);
            if(find_file != NULL){
                clear_data_block(find_file -> direct_ptr[0]);
                clear_inode_map(find_file -> ino);
            }
        }
    }
    //update father inode
    father_inode -> nlinks --;
    father_inode -> usize -= 32;
    father_inode -> mtime = get_timer();
    write_inode(father_inode -> ino);
    
    printk("\nFinish delete a directory!\n");
}

//File oeration